package com.hooker.hook;

import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import com.google.gson.JsonObject;
import com.hooker.base.BaseXposedLoader;
import com.hooker.common.HookHelper;
import com.hooker.common.Reflect;
import com.hooker.common.Reflection;
import com.hooker.common.StringUtil;
import com.hooker.listener.LaunchListener;
import com.hooker.settings.Setting;
import com.hooker.settings.SettingHelper;
import com.hooker.util.Constants;
import com.sdk.socket.SocketManager;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class HookerInject extends BaseXposedLoader {

    private Set<String> pgCache = new HashSet<>();

    public HookerInject(Context context, XC_LoadPackage.LoadPackageParam loadParams) {
        super(context, loadParams);
    }


    @Override
    public void hook(Object... calls) {
        hookMethod(Application.class, "onCreate", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                Log.d("HookerInject", "onCreate");
                Context context = (Context) param.thisObject;
                String pgName = context.getPackageName();
                String process = Setting.get(context, SettingHelper.getBuildInfo(pgName, Constants.HookType.HOOKER_INJECT.getId(), "process"));
                String currentName = StringUtil.getCurProcessName(context);
                if (TextUtils.isEmpty(process)) {
                    process = pgName;
                }

                if (TextUtils.isEmpty(process)) {
                    if (!pgCache.contains(pgName)) {
                        injectSdk(context);
                        pgCache.add(pgName);
                        log("HookerInject.onCreate on = " + currentName);
                    }
                } else if (process.equals(currentName)) {
                    injectSdk(context);
                    pgCache.add(pgName);
                    log("HookerInject.onCreate on = " + currentName);
                } else {
                    log("HookerInject.reject procecss = " + currentName);
                }
            }
        });
    }

    private void injectSdk(final Context ctx) {
        String packageName = loadParams.packageName;

        if (!isEnable(packageName, Constants.HookType.HOOKER_INJECT.getId())) {
            return;
        }

        List<String> injectValues = SettingHelper.getInjectValues(ctx, packageName);

        if (injectValues.isEmpty()) {
            return;
        }

        try {
            for (String inject : injectValues) {
                ApplicationInfo applicationInfo = ctx.getPackageManager().getApplicationInfo(inject, 0);
                if (applicationInfo != null) {
                    final String apkPackage = applicationInfo.packageName;
                    final String apkPath = applicationInfo.sourceDir;

                    final Context plgContext = ctx.createPackageContext(apkPackage, Context.CONTEXT_IGNORE_SECURITY);

                    try {
                        String filter = SocketManager.getInstance().getURLManager();
                        if (TextUtils.isEmpty(filter)) {
                            logE("获取URLManager失败,请先检查配置中心服务是否开启");
                            try {
                                InputStream open = plgContext.getAssets().open("pc.json");
                                String json = StringUtil.getFromAssetStream(open);
                                if (json != null) {
                                    JSONObject object = new JSONObject(json);
                                    String list = object.optString("list");
                                    log("获取pc.json成功,assert = " + list);
                                    hookAssetManager(plgContext, apkPackage, list.split(","));
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                                logE("获取pc.json失败,error=" + e.getMessage());
                            }
                        } else {
                            JSONObject object = new JSONObject(filter);
                            String list = object.optString("ASSERT_NAME");
                            log("获取URLManager成功,assert = " + list);
                            hookAssetManager(plgContext, apkPackage, list.split(","));
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        logE("请在asset中配置pc.json");
                    }

                    log("SDK:apkPth=" + apkPath);

                    final AtomicBoolean run = new AtomicBoolean(false);
                    LaunchListener.register(ctx, new LaunchListener.onReceiveListener() {
                        @Override
                        public void onReceive() {
                            try {
                                if (run.compareAndSet(false, true)) {
                                    PathClassLoader classLoader = new PathClassLoader(apkPath, ClassLoader.getSystemClassLoader());
                                    Class cls = classLoader.loadClass("com.hooker.InjectHelper");
                                    Reflection.call(null, cls, "inject", new Class[]{Context.class}, ctx);
                                }
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                    HookHelper.submitUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(ctx, "广播已注入", Toast.LENGTH_LONG).show();
                        }
                    });
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * hook assetManager
     *
     * @param context
     * @param apkPackage
     */
    private void hookAssetManager(final Context context, String apkPackage, String[] strings) {
        final List<String> list = Arrays.asList(strings);
        hookMethod(AssetManager.class, "open", String.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                Object o = param.thisObject;
                if (o == context.getResources().getAssets()) {
                    return;
                }
                String name = ((String) param.args[0]);
                log("hook.asset,open name=" + name);
                if (list.contains(name)) {
                    try {
                        param.setResult(context.getAssets().open(name));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    protected boolean isEnable(String pName, String typeName) {
        String tag = SettingHelper.getBuildInfo(pName, typeName);
        String state = Setting.get(context, tag);
        return "1".equals(state);
    }

    public static class HookApplication extends Application {
        private Context hookCtx;

        @Override
        public Resources getResources() {
            if (hookCtx != null) {
                return hookCtx.getResources();
            }
            return super.getResources();
        }

        @Override
        public AssetManager getAssets() {
            if (hookCtx != null) {
                return hookCtx.getAssets();
            }
            return super.getAssets();
        }

        public void setHookContext(Context ctx) {
            hookCtx = ctx;
        }
    }

}
